import React, { useContext, useLayoutEffect, useMemo, useState } from "react";
import lodash from "lodash";
import { ElmerService, IServiceResponse, IServiceSendOptions } from "./ElmerService";
import { IApiHandle, ServiceContext } from "./Context";

interface IServiceProps {
    children: any;
}

interface IApiRespHandle {
    isError: boolean;
    handler: IApiHandle[];
    response: IServiceResponse;
    resolve: Function;
    reject: Function;
    preventDefault: boolean;
    extra?: any
    sendOptions: IServiceSendOptions;
}
interface IApiHandleProps {
    handler: IApiHandle[];
    children: any[];
}

export type IServiceApi = Pick<ElmerService,"getConfig"|"getEndPoint"|"getUrl"|"send">;

const ApiContext = React.createContext<{ handler: IApiHandle[] }>({ handler: []});

const apiHandle = ({isError, handler, response, resolve, reject, ...extraData }: IApiRespHandle) => {
    try{
        let finalData = response.data;
        for(const callback of handler) {
            const opt = {
                isError,
                ...extraData
            };
            finalData = callback(finalData, response,opt);
            if(opt.preventDefault) {
                if(isError) {
                    return;
                }
                break;
            }
        };
        if(!isError) {
            resolve(finalData);
        } else {
            reject(finalData || response);
        }
    } catch(e) {
        reject(e);
    }
}

export const Provider = (props: IServiceProps) => {
    const [ serviceObj ] = useState(() => new ElmerService());
    const [ apiHandlerState ] = useState<{ uid: string[], callbacks: any}>({
        uid: [],
        callbacks: {}
    });
    const configContext = useContext(ServiceContext);
   
    const [configData ] = useState(configContext.config);
    const serviceApi = useMemo(() => ({
        send: (data: any, option?: any, extra?: any) => {
            const handlers: any[] = [...configContext.handler];
            apiHandlerState.uid.forEach((uid) => {
                handlers.push(...apiHandlerState.callbacks[uid]);
            });
            return new Promise((resolve, reject) => {
                serviceObj.send(data, option).then((resp: any) => {
                    apiHandle({
                        handler: handlers,
                        isError: false,
                        response: resp,
                        resolve,
                        reject,
                        preventDefault: false,
                        extra,
                        sendOptions: data
                    });
                }).catch((error) => {
                    apiHandle({
                        handler: handlers,
                        isError: true,
                        response: error,
                        resolve,
                        reject,
                        preventDefault: false,
                        extra,
                        sendOptions: data
                    });
                });
            });
        },
        getConfig: serviceObj.getConfig.bind(serviceObj),
        getEndPoint: serviceObj.getEndPoint.bind(serviceObj),
        getUrl: serviceObj.getUrl.bind(serviceObj)
    }), [apiHandlerState, configContext.handler, serviceObj]);
    const serviceContext = useMemo(() => ({
        service: serviceApi,
        config: configData,
        handler: configContext.handler,
        setApiHandler: (id: string, handler: IApiHandle[]) => {
            apiHandlerState.uid.push(id);
            apiHandlerState.callbacks[id] = handler;
            return () => {
                const rmIndex: number = apiHandlerState.uid.indexOf(id);
                rmIndex >= 0 && apiHandlerState.uid.splice(rmIndex, 1);
                delete apiHandlerState.callbacks[id];
            };
        }
    }), [ configData, serviceApi, configContext, apiHandlerState]);
    useLayoutEffect(() => {
        serviceObj.setConfig(configData);
    }, [configData, serviceObj]);
    return (
        <ServiceContext.Provider value={serviceContext}>
            { props.children }
        </ServiceContext.Provider>
    );
};

export const ApiHandleProvider = (props: IApiHandleProps) => {
    const apiContext = useMemo(()=>({
        handler: props.handler || []
    }), [props]);
    const { setApiHandler } = useContext(ServiceContext) as any;
    useLayoutEffect(() => {
        const fnId = lodash.uniqueId("ApiHandler");
        return setApiHandler(fnId, props.handler);
    }, []);
    return <ApiContext.Provider value={apiContext}>{props.children}</ApiContext.Provider>
}

export const useService = (): IServiceApi => {
    const configContext = useContext(ServiceContext) as any;
    return configContext.service;
};

